<?php

namespace tree;

/**
 * parent_id类型树结构相关
 * 没必要非要写成静态的方法，静态方法参数太多，所以用实例在构造函数中修改参数更合适
 * 需要首先将所有数据取出，然后再用此方法重新规划数组，其它的边取边查询数据库的方法不推荐
 * 经测试第一种方法要快很多，建议使用
 * @author   vishun <nadirvishun@gmail.com>
 */

class Tree
{
    /**
     * 图标
     */
    public $icon = '└';
    /**
     * 填充
     */
    public $blank = '&nbsp;&nbsp;&nbsp;';
    /**
     * 默认ID字段名称
     */
    public $idName = 'id';
    /**
     * 默认PID字段名称
     */
    public $pidName = 'pid';
    /**
     * 默认名称字段名称
     */
    public $titleName = 'name';
    /**
     * 默认子元素字段名称
     */
    public $childrenName = 'children';

    /**
     * 构造函数，可覆盖默认字段值
     * @param array $config
     */
    function __construct($config = [])
    {
        if (!empty($config)) {
            foreach ($config as $name => $value) {
                $this->$name = $value;
            }
        }
    }

    /**
     * 获取树形结构数据
     * */
    public function getTreeData($list, $pid = 0)
    {
        return $this->getTree($list, $pid);
    }

    /**
     * 生成下拉菜单可用树列表的方法
     * 经测试4000条地区数据耗时0.02左右，比另外两种方法快超级多
     * 流程是先通过引用方法来生成一种特殊树结构，再通过递归来解析这种特殊的结构
     * @param array $list
     * @param int   $pid
     * @param int   $level
     * @return array
     */
    public function getTreeOptions($list, $pid = 0, $level = 0)
    {
        //先生成特殊规格的树
        $tree = $this->getTree($list, $pid);
        return $this->formatTree($tree, $level);
    }

    /**
     * 通过递归来解析特殊的树结构来组装成下拉菜单所需要的样式
     * @param array $tree 特殊规格的数组
     * @param int   $level
     * @return array
     */
    protected function formatTree($tree, $level = 0)
    {
        $options = [];
        if (!empty($tree)) {
            $blankStr = str_repeat($this->blank, $level) . $this->icon;
            if ($level == 0) {//第一次无需有图标及空格
                $blankStr = '';
            }
            foreach ($tree as $key => $value) {
                $options[$value[$this->idName]] = $blankStr . $value[$this->titleName];
                if (isset($value[$this->childrenName])) {//查询是否有子节点
                    $optionsTmp = $this->formatTree($value[$this->childrenName], $level + 1);
                    if (!empty($optionsTmp)) {
                        //$options = array_merge($options, $optionsTmp);//发现一个问题，这里直接用array_merge会导致key重排
                        $options = $options + $optionsTmp;
                        //$options = ArrayHelper::merge($options, $optionsTmp);//如果是用yii2带话可以用助手类，需要use其命名空间
                    }
                }
            }
        }
        return $options;
    }

    /**
     * 生成类似下种格式的树结构
     * 利用了引用&来实现，参照：http://blog.csdn.net/gxdvip/article/details/24434801
     * [
     *  'id'=>1,
     *  'pid'=>0,
     *  'items'=>[
     *      'id'=>2,
     *      'pid'=>'1'
     *       ...
     *  ]
     * ]
     * @param array $list
     * @param int   $pid
     * @return array
     */
    protected function getTree($list, $pid = 0)
    {
        $tree = [];
        if (!empty($list)) {
            //先修改为以id为下标的列表
            $newList = [];
            foreach ($list as $k => $v) {
                $newList[$v[$this->idName]] = $v;
            }
            //然后开始组装成特殊格式
            foreach ($newList as $value) {
                if ($pid == $value[$this->pidName]) {
                    $tree[] = &$newList[$value[$this->idName]];
                } elseif (isset($newList[$value[$this->pidName]])) {
                    $newList[$value[$this->pidName]][$this->childrenName][] = &$newList[$value[$this->idName]];
                }
            }
        }
        return $tree;
    }
}